package com.chen.webservice.service.impl;

import com.chen.webservice.Utils.RuleConstants;
import com.chen.webservice.exception.ServiceException;
import com.chen.webservice.model.CheckCoverProgramCanPlayParameterDTO;
import com.chen.webservice.model.CheckOtherProgramCanPlayParameterDTO;
import com.chen.webservice.model.MyOrder;
import com.chen.webservice.model.Order;
import com.chen.webservice.service.ProgramCheckService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;

@Slf4j
@Service
public class ProgramCheckServiceImpl implements ProgramCheckService {

    @Override
    public Boolean checkCoverProgram(CheckCoverProgramCanPlayParameterDTO dto) {
        int openTime = dto.getFixProgram().getIsArrage() ? dto.getOpenTime() + dto.getFixProgram().getPlayTime() : dto.getOpenTime();   // 开屏时间
        int closeTime = dto.getFixProgram().getIsArrage() ? dto.getCloseTime() - dto.getFixProgram().getPlayTime() : dto.getCloseTime();    // 关屏时间

        // 校验包屏广告是否允许播放
        Order newOrder = dto.getNewOrder();
        if (newOrder.getPlayType().equals(RuleConstants.PlayType.COVER.getValue())) {
            for (Order order : dto.getOrders()) {
                if (order.getPlayType().equals(RuleConstants.PlayType.COVER.getValue())) {
                    // 判断待排单广告是否在轮播广告的左侧
                    Boolean checkNewOrderArrangeLeft = newOrder.getStartTime() < order.getStartTime() && newOrder.getEndTime() <= order.getStartTime();
                    Boolean checkNewOrderArrangeRight = newOrder.getStartTime() >= order.getEndTime() && newOrder.getEndTime() > order.getEndTime();
                    // 判断待排单广告是否在轮播广告的右侧
                    if (!(checkNewOrderArrangeLeft || checkNewOrderArrangeRight)) {
                        // 不能插入
                        String errorMsg = "与" + order.getName() + "广告播放时间发生冲突，不允许播放";
                        throw new ServiceException(errorMsg);
                    } else {
                        // 判断是否在允许播放的时间段内
                        Integer endTime = dto.getOnlyArrangeFirst30Minute() ? openTime + 1800 : closeTime;
                        if (!(newOrder.getStartTime() >= openTime && newOrder.getStartTime() < endTime &&
                                newOrder.getEndTime() > openTime && newOrder.getEndTime() <= endTime)) {
                            // 不能插入
                            String errorMsg = "与开屏和关屏时间发生冲突，不允许播放";
                            throw new ServiceException(errorMsg);
                        }
                    }
                }
            }
        }

        return true;
    }

    @Override
    public Boolean checkOtherProgram(CheckOtherProgramCanPlayParameterDTO dto) {
        for (CheckOtherProgramCanPlayParameterDTO.OneDayOrderData oneDayOrderData : dto.getOrderDataList()) {
            int openTime = oneDayOrderData.getFixProgram().getIsArrage() ? oneDayOrderData.getOpenTime() + oneDayOrderData.getFixProgram().getPlayTime() : oneDayOrderData.getOpenTime();   // 开屏时间
            int closeTime = oneDayOrderData.getFixProgram().getIsArrage() ? oneDayOrderData.getCloseTime() - oneDayOrderData.getFixProgram().getPlayTime() : oneDayOrderData.getCloseTime();    // 关屏时间

            // 校验其他广告是否允许播放
            long resTime = checkOtherProgramCanPlay(openTime, closeTime, dto.getNewOrder(), oneDayOrderData.getOrders(), oneDayOrderData.getOnlyArrangeFirst30Minute(), dto.getBusinessRate());
            if (resTime < 0) {
                throw new ServiceException("第" + oneDayOrderData.getDate() + "天，该广告播放时长超时，超时: " + -resTime + "秒!");
            }
        }

        return true;
    }

    /**
     * 校验其他广告是否允许播放
     */
    private long checkOtherProgramCanPlay(int openTime, int closeTime, Order newOrder, List<Order> orders, Boolean onlyArrangeFirst30Minute, Double businessRate) {
        if (onlyArrangeFirst30Minute) {
            closeTime = openTime + 1800;
        }
        MyOrder base = new MyOrder(openTime, closeTime, orders);

        processFreq(base);
        return processTime(base, newOrder, onlyArrangeFirst30Minute, businessRate);
    }

    /**
     * 处理包屏、原创广告和定时广告(打乱顺序)
     *
     * @param myOrder
     */
    private static void processFreq(MyOrder myOrder) {
        Random random = new Random();

        List<Order> orders = myOrder.getOrders();
        List<Order> freqOrders = new ArrayList<>();
        Iterator<Order> iterator = orders.iterator();

        List<Order> coverOrders = new ArrayList<>();
        while (iterator.hasNext()) {
            Order order = iterator.next();
            if (order.getPlayType() == RuleConstants.PlayType.COVER.getValue()) {
                coverOrders.add(order);
                iterator.remove();
                continue;
            }
            if (order.getPlayFrequency() <= 1) {
                continue;
            }
            for (int i = 0; i < order.getPlayFrequency() - 1; i++) {
                Order freqOrder = order.deepCopy();
                int index = random.nextInt(freqOrders.size()+1);
                try {
                    freqOrders.add(index, freqOrder);
                } catch (Exception e) {
                    log.error("index: {}", index);
                }
            }
        }
        myOrder.getCovOrders().addAll(coverOrders);

        orders.addAll(freqOrders);
        Collections.shuffle(orders);
    }

    /**
     * 判断新广告能否被插入
     */
    private long processTime(MyOrder base, Order newOrder, Boolean onlyArrangeFirst30Minute, Double businessRate) {
        List<Order> orders = base.getOrders();   // 定时广告
        Iterator<Order> iterator = orders.iterator();

        List<Order> coverOrders = base.getCovOrders();

        long totalPlayTime = base.getEndTime() - base.getStartTime();
        // 根据总播放时间，添加满足播放时长的定时广告和原创广告
        return fillTime(totalPlayTime, orders, coverOrders, newOrder, onlyArrangeFirst30Minute, base.getStartTime(), businessRate);
    }

    /**
     * 填充播放时间
     */
    private long fillTime(long totalPlayTime, List<Order> orders, List<Order> coverOrders, Order newOrder,
                          Boolean onlyArrangeFirst30Minute, Integer startTime, Double businessRate) {
        if (totalPlayTime <= 0) {
            throw new ServiceException("时间不正确，播放结束时间小于播放开始时间");
        }

        List<Order> resOrders = new ArrayList<>();
        long resPlayTime;

        if (onlyArrangeFirst30Minute) {
            // 只排前30分钟广告
            Integer endTime = startTime + 30 * 60;
            coverOrders.forEach(coverOrder -> {
                if (coverOrder.getStartTime() >= startTime && coverOrder.getEndTime() <= endTime) {
                    resOrders.add(coverOrder);
                }
            });
            int orderTime = getPlayedTime(resOrders);
            resPlayTime = totalPlayTime - orderTime;    // 总播放时间 - 包屏时间
        } else {
            // 设定的播放时间段内都需要排单
            resPlayTime = arrangeAllTime(totalPlayTime, coverOrders, resOrders);
        }

        if (resPlayTime > 0) {  // 包屏时间未用完
            long resTime = shuffleOrder(orders, newOrder, resPlayTime, businessRate, totalPlayTime);
            return resTime;
        } else {
            throw new ServiceException("包屏广告已经占满所有广告播放时间，无法播放该条广告!");
        }
    }

    /**
     * 校验商业广告是否能进行播放
     */
    private long shuffleOrder(List<Order> orders, Order newOrder, long resPlayTime, Double businessRate, long totalPlayTime) {
        List<Integer> num = new ArrayList<>();
        for (int i = 0; i < orders.size(); i++) {
            num.add(i);
        }

        long businessProgramPlayTime = 0;

        Collections.shuffle(num);

        // 轮播广告添加满足时间条件的定时广告
        for (int i = 0; i < num.size(); i++) {
            Order order = orders.get(i);
            businessProgramPlayTime += order.getPlayTime();
            if (Objects.nonNull(businessRate)) {
                if ((double) businessProgramPlayTime / totalPlayTime > businessRate) {
                    throw new ServiceException("已达到设定的商业广告占比，该广告不允许被播放!");
                }
            }
            if (resPlayTime - order.getPlayTime() > 0) {
                resPlayTime -= order.getPlayTime();
            }
        }

        businessProgramPlayTime += newOrder.getPlayTime();

        if (Objects.nonNull(businessRate)) {
            if ((double) businessProgramPlayTime / totalPlayTime > businessRate) {
                throw new ServiceException("已达到设定的商业广告占比，该广告不允许被播放!");
            }
        }
        return resPlayTime - newOrder.getPlayTime();
    }

    /**
     * 计算包屏总时间
     */
    private int getPlayedTime(List<Order> orders) {
        int time = 0;
        if (CollectionUtils.isEmpty(orders)) {
            return time;
        }
        time = orders.stream().mapToInt(Order::getPlayTime).sum();
        return time;
    }

    /**
     * 设定的播放时间段内都需要排单
     */
    private long arrangeAllTime(long totalPlayTime, List<Order> coverOrders, List<Order> resOrders) {
        long resPlayTime;
        resOrders.addAll(coverOrders);
        int orderTime = getPlayedTime(coverOrders);
        resPlayTime = totalPlayTime - orderTime;    // 总播放时间 - 包屏时间
        return resPlayTime;
    }
}
